(function(global) {
  /**
   * Create a template with variable display and basic logic and looping
   * @name pulp.template
   * @requires base
   * @requires cls
   * @example
   *   <?php ob_start();?>
   *     {if $data.colors.length}
   *       <ul>
   *         {foreach $data.colors as $i => $color}
   *           <li class="{echo ($i % 2) == 0 ? 'even' : 'odd'}">{$color.toUpperCase()}</li>
   *         {/foreach}
   *       </ul>
   *     {else}
   *       <p>No Colors Found</p>
   *     {/if}
   *   <?php $html = ob_get_clean();?>
   *   <script type="text/javascript">
   *     var html = '<?php str_replace("'", "\\'", $html)?>';
   *     var tpl = new pulp.Template(html);
   *     var colorData = {colors: ['red', 'green', 'blue']};
   *     $(myResults).innerHTML = tpl.evaluate(colorData);
   *   </script>
   */
	var hasOwn = Object.prototype.hasOwnProperty,
		toStr = Object.prototype.toString;

	function extend(d, s) {
		for (var p in s) {
			if (hasOwn.call(s, p)) {
				d[p] = s[p];
			}
		}
		return d;
	}
	function trim(s) {
		return s.replace(/^\s+/, '').replace(/\s+$/, '');
	}
	function inArray(a, v) {
		if (Array.prototype.indexOf) {
			return a.indexOf(v) > -1;
		}
		var i = a.length;
		while (i--) {
			if (a[i] == v) {
				return true;
			}
		}
		return false;
	}
	function escapeHTML(s) {
		return string.replace(/&/g, '&amp;').replace(/>/g, '&gt;').replace(/</g, '&lt;');
	}
	/**
	 * Construct new instance--Optionally set and compile the template for later evaluation
	 *
	 * @constructs
	 * @param {String} [template=""]  The template to interpolate
	 * @param {Object} [options]  Parser syntax options
	 * @param {String} [options.open="{"]  The open tag (e.g. "#{", "<?", "<%", etc.)
	 * @param {String} [options.close="}"]  The close tag (e.g. "#}, "?>", "%>", etc.)
	 * @param {String} [options.variable="$"]  The variable character(s) (e.g. "$", "&", "@", etc.)
	 * @param {String} [options.assign={}]  The initial assignments if any
	 */
  function Template(template, options) {
		options = options || { };
		/**
		 * Internal hash of assignments
		 * @name pulp.template#_assignments
		 * @type {Object}
		 */
		this._assignments = options.assign || { };

		/**
		 * The variable character(s) (e.g. "$", ":", "@", etc.)
		 * @name pulp.template#_variable
		 * @type {String}
		 */
		this._variable = options.variable || Template.defaultOptions.variable;
		/**
		 * The open tag (e.g. "#{", "<?", "<%", etc.)
		 * @name pulp.template#_openTag
		 * @type {String}
		 */
		this._openTag = Template._regExpEscape(options.open || Template.defaultOptions.open);
		/**
		 * The close tag (e.g. "}, "?>", "%>", etc.)
		 * @name pulp.template#_closeTag
		 * @type {String}
		 */
		this._closeTag = Template._regExpEscape(options.close || Template.defaultOptions.close);
		var escapedVariable = Template._regExpEscape(this._variable);
		var regex = '(' + this._openTag +
			// allow {/*} or {*/} or {tagname expression} or {/tagname} or {$var} or {$var.modifier()}
			// and tagnames must be 2 to 20 characters beginning with a letter
			// and variables must begin with a letter
			 '(?:\\/\\*|\\*\\/|[a-z][\\w_-]{1,20}.*?|\\/[a-z][\\w_-]{1,20}|' + escapedVariable + '[a-z][^' + this._closeTag + ']*)' +
			 this._closeTag + ')';
		/**
		 * The regular expression used to separate tags and content in between
		 * @name pulp.template#_tagSplitter
		 * @type {RegExp}
		 */
		this._tagSplitter = new RegExp(regex, 'i');
		/**
		 * The regular expression used to match variables referenced within tag attributes
		 * @name pulp.template#_tagSplitter
		 * @type {RegExp}
		 */
		this._varMatcher = new RegExp(escapedVariable + '([a-z][\\w]*)', 'g');
		/**
		 * A String consisting of JavaScript code, that, when executed, returns the evaluated template
		 * @name pulp.template#_compiled
		 * @type {String}
		 */
		this._compiled = '';

		// compile the template if given
		if (template) {
			this.compile(template+'');
		}
	}
	extend(Template.prototype, {
    /**
     * Set and compile the template for later evaluation
     *
     * @param {String} [template=""]  The html or simple-string template
     * @return {pulp.template}
     * @throws {Error}  When template contains an unrecognized tag
     */
    compile: function compile(template) {
      this._compiled = '';

      // to facilitate tracking line no, normalize line endings to \n
      template = template.replace((/(\r\n|\r)/g), '\n');
      // Split the template into a stack of text and tokens
      var stack = Template._splitAndCapture(typeof template == 'undefined' ? '' : template, this._tagSplitter);
      /**
       * The line number of the current tag
       * @name pulp.template#_lineNo
       * @type {Number}
       */
      this._lineNo = 1;
      for (var i = 0, length = stack.length; i < length; i++) {
        // odd items are tokens
        if (i % 2 == 1) { // odd
          // we jave a token: test which kind
          if (stack[i].substring(this._openTag.length, this._variable.length) == this._variable) {
            // we have a simple variable: just concatenate
            this._compiled += Template._tagHandlers._variable.call(this, stack[i].substring(this._openTag.length - 1, stack[i].length - this._closeTag.length + 1));
          } else {
            match = /\{(\S+)(?:\s(.+))?\}/.exec(stack[i]);
            // we have a language construct: find our handler
            if (typeof Template._tagHandlers[match[1]] == 'function') {
              // execute handler
              this._compiled += Template._tagHandlers[match[1]].call(this, match[2]);
            } else {
              // error: handler not found
              throw new Error('pulp.template Exception: tag name "' + (match[1] || stack[i]) + '" not recognized on line ' + this._lineNo);
            }
          }
        // for speed, ensure the content isn't an empty string
        } else if (stack[i] != '') {
          // Concatenate plain template text
          this._compiled += Template._tagHandlers._content.call(this, stack[i]);
        }
        this._updateLineNo(stack[i]);
      }
      this._lineNo = 1;
      return this;
    },
    /**
     * Assign a value to use every time evaluate() is called for this instance
     *
     * @param {String} name  The property to which to assign the value
     * @param {Mixed} value  The value to assign
     * @return {pulp.template}
     * @chainable
     */
    assign: function assign(name, value) {
			if (toStr.call(name) == '[object String]') {
				extend(this._assignments, name);
			}
			else {
				this._assignments[name] = value;
			}
      return this;
    },
    /**
     * Remove an assignment
     *
     * @param string name  The assignment to remove
     * @return {pulp.template}
     * @chainable
     */
    unassign: function unassign(name) {
			if (arguments.length > 0) {
				delete this._assignments[name];
			}
			else {
				this._assignments = {};
			}
      return this;
    },
    /**
     * Evaluate the template given an object
     *
     * @param object obj  The objects whose properties to substitute for tokens
     * @return string
     */
    evaluate: function evaluate(obj) {
      // add in our custom assignments
      obj = extend(extend({}, this._assignments), obj || {});
      // initialize our template output
      var output = '';
      // evaluate the compiled template in the presence of our object assignments
      eval(this._compiled);
      // return the output
      return output;
    },
    /**
     * Return the raw JavaScript compilation used for eval
     */
    getCompilation: function getCompilation() {
      return this._compiled;
    },
		toString: function toString() {
			return this.evaluate();
		},
		valueOf: function valueOf() {
			return this.evaluate();
		},
    /**
     * Replace variables and allow modifiers to be used (e.g. $name.toUpperCase())
     *
     * @param string expression  The template code
     * @return string
     */
    _interpretVars: function _interpretVars(expression) {
      return expression.replace(this._varMatcher, "(typeof obj.$1 == 'undefined' || obj.$1 === null ? '' : obj.$1)");
    },
    /**
     * Convert field=value pairs into an object
     *
     * @param string expression
     * @return object
     *
     * @example
     * this._extractParams('a=1 b=2 c=3'); // {a: 1, b: 2, c: 3}
     * this._extractParams('a="1" b="2" c="3"'); // {a: 1, b: 2, c: 3}
     * this._extractParams('a = "1" b = "2" c = "3"'); // {a: 1, b: 2, c: 3}
     */
    _extractParams: function _extractParams(expression) {
      var stack = Template._splitAndCapture(expression, /([a-zA-Z][\w_]* ?=)/);
      var params = {}, param, value;
      for (var i = 0, length = stack.length; i < length; i++) {
        if (i % 2 == 1) { // odd
          param = trim(stack[i].substring(0, stack[i].length - 1));
        } else {
          value = trim(stack[i]);
          if (value) {
            params[param] = this._replaceVar(value.replace((/^("|')(.*)\1$/g), '$2'));
          }
        }
      }
      return params;
    },
    /**
     * Replace variables and DO NOT allow modifiers to be used (e.g. $name.toUpperCase())
     *
     * @param string string
     * @return string
     */
    _replaceVar: function _replaceVar(expression) {
      return expression.replace(this._varMatcher, "obj.$1");
    },
    /**
     * Update the line number by counting the number of newlines
     * @param {String} str
     * @return {undefined}
     */
    _updateLineNo: function _updateLineNo(str) {
      this._lineNo += (str.length - str.replace((/\n/g), '').length);
    }
  });

  extend(Template,/** @scope pulp.template */{
  	/**
  	 * Default options for template syntax
  	 * @type {Object}
  	 */
  	defaultOptions: {
  	  open: '{',
  	  close: '}',
  	  variable: '$'
  	},
    /**
     * Add or reset a tag handling function
     *
     * @note  tags must be 2 to 16 characters beginning with a letter
     * @param string name  The tag name
     * @param function callback  The handling function
     * @return pulp.template object
     */
    setTagHandler: function setTagHandler(name, callback) {
      Template._tagHandlers[name] = callback;
      return this;
    },
    // helper to create input tags for radio boxes
    _helpRadioCheckbox: function _helpRadioCheckbox(type, expression) {
      var params = this._extractParams(expression);
      return '(function(name, values, currOutput, options, selected, separator) {\n' +
        Template._helpRadioCheckboxOptions('checked') +
        'output += \'<label><input type="' + type + '" name="\' + name + \'" value="\' + escapeHTML(value) + \'"\' + sel + " /> " + escapeHTML(options[value]) + "</label>" + separator + "\\n";\n' +
        '} })(' + Template._addQuotesIfNeeded(params.name || 'radio') + ',' + this._interpretVars(params.values || 'null') + ',' + this._interpretVars(params.currOutput || 'null') + ',' + this._interpretVars(params.options || 'null') + ',' + this._interpretVars(params.selected || '[]') + ',' + tpl._addQuotesIfNeeded(params.separator || '<br />') + ');\n'
      ;
    },

    // helper to create iteration code for radios, checkboxes, and option tags
    _helpRadioCheckboxOptions: function _helpRadioCheckboxOptions(type) {
      return 'if (values && currOutput) {\n' +
        'var options = {};\n' +
        'for (var i = 0, length = values.length; i < length; i++)\n' +
        'options[values[i]] = currOutput[i];\n' +
        '} else {\n' +
        'var options = options || {};\n' +
        '}\n' +
        'var sel;\n' +
        'for (var value in options) {\n' +
        'if (!hasOwn.call(options, value)) { continue; }\n' +
        'if (toStr.call(selected) == "[object Array]") sel = (inArray(selected, value) ? \' ' + type + '="' + type + '"\' : "");\n' +
        'else sel = (selected+"" === value+"" ? \' ' + type + '="' + type + '"\' : "");\n'
      ;
    },
    /**
     * Wrap a string with quotes if not present
     *
     * @param string string
     * @return string
     */
    _addQuotesIfNeeded: function _addQuotesIfNeeded(string) {
      return (/^(['"]).*\1/.test(string) ? string : '"' + string.replace('"', '\\"') + '"');
    },
    /**
     * Escape a string for use with single quote and single lines
     *
     * @param string string
     * @return string
     */
    _escapeQuotes: function _escapeQuotes(string) {
      return string.replace(/'/g, "\\'").replace(/\n/g, "\\n");
    },
    /**
     *
     */
    _regExpEscape: function _regExpEscape(str) {
      return (str+'').replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
    },
    _splitAndCapture: function _splitAndCapture(subject, splitter) {
      return subject.split(splitter);
    },
  	/**
  	 * A function to handle each tag name, variables, and content.
  	 *   Each handler must return a string containing javascript code.
  	 *   Content is contained in the variable "output" and thus can be manipulated.
  	 * @type {Object}
  	 */
  	_tagHandlers: /** @scope pulp.template._tagHandlers */{
  		/**
  		 * Handler for adding simple variables to the template output
  		 */
  		'_variable': function(expression) {
  			return "output += " + this._replaceVar(expression) + ";\n";
  		},
  		/**
  		 * Handler for adding plain content to the template output
  		 */
  		'_content': function(content) {
  		  if (this.isInEval) {
  		    return content;
  		  } else {
    			// escape content for strings
    			return "output += '" + Template._escapeQuotes(content) + "';\n";
  		  }
  		},
  		/**
  		 * Handler for comment tag open
  		 */
  		'/\*': function() { return '\n/*\n'; },
      /**
       * Handler for comment tag close
       */
  		'*\/': function() { return '\n*/\n'; },
  		/**
  		 * If Logic: an if clause followed by zero or more elseif clauses optionally followed by else
  		 *
  		 * @syntax  {if $bool} content {/if}
  		 *   adds content if $bool is true
  		 *
  		 * @syntax  {if $bool1} content1 {else} content2 {/if}
  		 *   adds content1 if $bool1 is true, content2 otherwise
  		 *
  		 * @syntax  {if $bool1} content1 {elseif $bool2} content2 {else} content3 {/if}
  		 *   adds content 1 if $bool1 is true, content2 if $bool2 is true, content3 otherwise
  		 *
  		 * @throws {Error} When "if" or "elseif" tag contains no expression
  		 */
  		'if': function(expression) {
  			if (!expression) throw new Error('pulp.template Exception: {if} tag cannot be blank on line ' + this._lineNo);
  			return 'if (' + this._interpretVars(expression) + ') {\n';
  		},
  		'elseif': function(expression) {
  			if (!expression) throw new Error('pulp.template Exception: {elseif} tag cannot be blank on line ' + this._lineNo);
  			return '} else if (' + this._interpretVars(expression) + ') {\n';
  		},
  		'else': function() {
  			return '} else {\n';
  		},
  		'/if': function() {
  			return '}\n';
  		},
  		/**
  		 * "foreach" Looping: for loop or for-in loop
  		 *
  		 * @example {foreach $items}
  		 *   execute one time for each item in $items
  		 *
  		 * @example {foreach $items as $item}
  		 *   execute one time for each item in $items, with a reference to the current item in $item
  		 *
  		 * @example {foreach $items as $idx => $item}
  		 *   execute one time for each item in $items, with a reference to the iteration number in $idx and the current item in $item
  		 *
  		 * @example {foreach $property in $object}
  		 *   execute one time for each property in $object, with a reference to the property name in $property
  		 *
  		 * @example {foreach $property in $object as $item}
  		 *   execute one time for each property in $object, with a reference to the property name in $property and the property value referenced in $item
  		 */
  		'foreach': function(options) {
  			var compiled = '', alias, varName;
  			// find output if a "for" or "for in" loop is desired
  			var type = options.split(' in ');
  			if (type[1]) {
  				// "for in" loop
  				// get alias if present
  				alias = type[1].split(' as ');
  				varName = this._replaceVar(alias[0]);
  				compiled += '(function() { for (var property in ' + varName + ') {\n';
  				compiled += 'if (!hasOwn.call(' + varName + ',property)) { continue; }\n';
  				compiled += this._replaceVar(type[0]) + ' = property;\n';
  				// do we have an alias?
  				if (alias[1]) {
  					// yes, assign the item name to the value of this property
  					compiled += this._replaceVar(alias[1]) + ' = ' + this._interpretVars(type[0]) + '[property];\n';
  				}
  			} else {
  				// "for" loop
  				// get alias if present
  				alias = type[0].split(' as ');
  				varName = this._replaceVar(alias[0]);
  				compiled += '(function() { for (var i = 0, len = (' + varName + ' ? ' + varName + '.length : 0); i < len; i++) {\n';
  				// do we have an alias?
  				if (alias[1]) {
  					// yes alias
  					// get pair or single
  					var pair = alias[1].split(' => ');
  					// do we have a pair?
  					if (pair[1]) {
  						// yes, assign the first item name to i
  						compiled += this._replaceVar(pair.shift()) + ' = i;\n';
  					}
  					// add the alias to the item
  					compiled += this._replaceVar(pair[0]) + ' = ' + varName + '[i];\n';
  				}
  			}
  			return compiled;
  		},
  		'/foreach': function() {
  			return '}})();\n';
  		},
  		/**
  		 * Include the output of another template object
  		 *
  		 * @example - allows encapsulation or recursion
  		 *   var tpl1 = new pulp.Template('{$msg}');
  		 *   tpl1.assign('msg', 'Hello World');
  		 *   var tpl2 = new pulp.Template('My message: {include $template}');
  		 *   tpl2.assign('template', tpl1);
  		 *   tpl2.evaluate(); // "My message: Hello World"
  		 */
  		'include': function(expression) {
  			return 'output += ' + this._replaceVar(expression) + '.evaluate(this._assignments);\n';
  		},
  		/**
  		 * Capture output to be used later
  		 *
  		 * @example - create a row of empty columns
  		 *   {capture name="emptyTd"}
  		 *     <td>N/A</td>
  		 *   {/capture}
  		 *   {foreach $columns as $column}
  		 *     {if $column}
  		 *       <td>{$column}</td>
  		 *     {else}
  		 *       {$emptyTd}
  		 *     {/if}
  		 *   {/foreach}
  		 *
  		 *   Alternate usage: {capture emptyTd} OR {capture}
  		 */
  		'capture': function(expression) {
  		  this._captureName = this._extractParams(expression).name || expression || 'global';
  			return 'obj.__' + this._captureName + ' = output;\n' +
  				"output = '';\n"
  			;
  		},
  		'/capture': function(expression) {
  			return 'var preCaptureOut = obj.__' + this._captureName + ';\n' +
  				'obj.__' + this._captureName + ' = output;\n' +
  				'output = preCaptureOut;\n'
  			;
  		},
  		/**
  		 * Assign a variable to be used later
  		 *
  		 * @example - process an upper-case value once
  		 *   {assign $upperName = $name.toUpperCase()}
  		 *   {$upperName}
  		 *
  		 * @example - process using a function (note the optional $ on the assignee
  		 *   {assign processed=myGlobalFn($value)}
  		 *   {$processed}
  		 */
  		'assign': function(expression) {
  			var parts = expression.split(/\s*=\s*/);
  			return this._replaceVar(parts[0]) + '=' + this._interpretVars(parts[1]) + ';\n';
  		},
  		/**
  		 * Execute and output the results of a JavaScript expression, substituting variables if needed
  		 *
  		 * @example - do division
  		 *   {$numTests} tests executed in {$execTime} seconds: average of {echo Math.round($numTests / $execTime, 2)} seconds per iteration
  		 */
  		'echo': function(expression) {
  			return 'output += ' + this._interpretVars(expression) + ';\n';
  		},
  		/**
  		 * Specify javascript to be run; it will be in the scope of the instance and have access to "output";
  		 *
  		 * @example - observe node
  		 *   <p id="myp3">When clicked, you should see a hello alert</p>{script delay="20"}$("myp3").onclick = function() { alert("hello"); }{/script}
  		 *
       * @example - directly alter output
       *   <p>He has {script} output += (obj.mice > 9 ? 'too many' : 'just enough');{/script} mice</p>
  		 */
  		'script': function(expression) {
  		  this.isInEval = true;
  		  var match = (/delay\s*\=\s*("|'|)(\d+)\1/).exec(expression);
  		  if (match) {
  		    this.delayed = match[2];
  		    return 'window.setTimeout(function() {\nvar output = "";\n';
  		  } else {
  		    return '';
  		  }
  		},
  		'/script': function() {
  		  var ret = '';
  		  if (typeof this.delayed == 'undefined') {
  		    ret = '}, ' + this.delayed + ')';
  		    delete this.delayed;
  		  }
  		  this.isInEval = false;
  			return ret;
  		},
  		// 'cycle':
  		//
  		/**
  		 * Output a series of <option> tags with one or more options selected
  		 *
  		 * @example - do division
  		 *
  		 */
  		'options': function(expression) {
  			var params = this._extractParams(expression);
  			return '(function(values, currOutput, options, selected) {\n' +
  				Template._helpRadioCheckboxOptions('selected') +
  				'output += \'<option value="\' + escapeHTML(value) + \'"\' + sel + \'>\' + escapeHTML(options[value]) + \'</option>\\n\';\n' +
  				'} })(' + this._interpretVars(params.values || 'null') + ',' + this._interpretVars(params.currOutput || 'null') + ',' + this._interpretVars(params.options || 'null') + ',' + this._interpretVars(params.selected || '[]') + ');\n'
  			;
  		},
  		'radios': function(expression) {
  			return Template._helpRadioCheckbox.call(this, 'radio', expression);
  		},
  		'checkboxes': function(expression) {
  			return Template._helpRadioCheckbox.call(this, 'checkbox', expression);
  		}
  	}
  });

  // some browsers (such as IE) don't allow you to capture the split delimiter
  //   so create a generic function to split a string and capture the split tokens
  if ('abc'.split(/(b)/)[1] != 'b') {
		/** @ignore */
    Template._splitAndCapture = function _splitAndCapture(subject, splitter) {
      var stack = [], match;
      while (subject.length > 0) {
        if (match = subject.match(splitter)) {
          stack.push(subject.slice(0, match.index));
          stack.push(subject.slice(match.index, match.index + match[0].length));
          subject = subject.slice(match.index + match[0].length);
        } else {
          stack.push(subject);
          subject = '';
        }
      }
      return stack;
    }
  }

	// create ProtoLove if not exists
	global.ProtoLove = global.ProtoLove || {};
	// export Template
	global.ProtoLove.Template = Template;
  global.Template = global.Template || Template;

	var compile = null,
		assign = null,
		unassign = null,
		evaluate = null,
		getCompilation = null,
		toString = null,
		valueOf = null,
		_interpretVars = null,
		_extractParams = null,
		_replaceVars = null,
		_updateLineNo = null,
		setTagHandler = null,
		_helpRadioCheckbox = null,
		_helpRadioCheckboxOptions = null,
		_addQuotesIfNeeded = null,
		_escapeQuotes = null,
		_regExpEscape = null,
		_splitAndCapture = null;

})(this);
